import random
import time
import math
import copy
PI = 3.1415926
NUMIND = 25

crossPro = 0.9
mutaPro = 0.1


#目标函数
def fun(x,FUN):
    if FUN == 1:
        return 3*(x[0]**2-x[1])**2
    if FUN == 2:
        return x[0] * math.sin(10 * PI * x[0]) + 2.0

def check_border(x):
    if x>up:
        return up
    if x<low:
        return low
    return x

# 1.实数编码 2.交叉近亲回避 3.两点变异 4.多目标优化 5.目标函数用户输入
'''定义个体结构'''
class Indivi():
    def __init__(self):
        self.fitness = 0.0
        self.x = [0.0]*10 #参数空间最大为100
        self.x2d = ['0' for i in range(25)]
        self.upLim = 0.0
'''定义种群结构'''
class Popu:
    def __init__(self):
        self.indv = [Indivi() for i in range(NUMIND)]
        self.bestInd = Indivi()
        self.generan = 0

pop = Popu()


def ranF(x, y):
    return (((float(random.randint(0, 32767)) + 90.0) * (y - x) / 33956.0) + x)


# 目标函数 y=x·sin(10·PI·x)
def fit(x, i):
        return fun(x,i)

# 初始化种群
def initPop():
    pop.generan = 0
    for i in range(NUMIND):
        for j in range(x_dim):
            pop.indv[i].x[j] = random.uniform(low, up)

# 转换到[-1,2]
def calFit():
    for i in range(NUMIND):
        pop.indv[i].fitness = fit(pop.indv[i].x,FUN)

# upLim轮盘赌上界
# temp为下一个种群个体的适应值除以总适应值，同时上界为:temp+pop.indv[i].upLim
# 如果种群个体还没到最后一个，但上界已经超过了1.0，就输出错误信息，并推出exit(0)
# 如果没有错误，则最后一个的上界赋值为 1
def calUp():
    Sum = sum([pop.indv[i].fitness for i in range(NUMIND)])
    pop.indv[0].upLim = pop.indv[0].fitness / Sum
    for i in range(NUMIND - 1):
        temp = pop.indv[i + 1].fitness / Sum
        pop.indv[i + 1].upLim = temp + pop.indv[i].upLim
        if pop.indv[i + 1].upLim > 1.0 and (i + 1) < (NUMIND - 1):
            for j in range(NUMIND):
                print(
                    f"generation is: {pop.generan}, fitness = {pop.indv[j].fitness}, UpLim = {pop.indv[j].upLim}\n"
                )
            print(f"\nError,{i+1}'s upLim is greater than 1=============\n")
            exit(0)
    pop.indv[NUMIND - 1].upLim = 1

# 一个已排序的列表 InA 中查找一个值 value 的位置，并返回该位置的索引，LowBo：下限, UpBo：上限
def HalfSear(value, LowBo, UpBo, InA):
    Mid = (LowBo + UpBo) // 2
    if Mid == 0:
        return 0
    if (value <= InA[Mid]) and (value > InA[Mid - 1]):
        return Mid
    else:
        if value >= InA[Mid]:
            return HalfSear(value, Mid, UpBo, InA)
        else:
            return HalfSear(value, LowBo, Mid, InA)

# TemFitn列表存储每个个体在轮盘赌中被选中的概率
# 将pop.indv[NumTemp]设为新个体
def calSub():
    TemFitn = [pop.indv[i].upLim for i in range(NUMIND)]
    for i in range(NUMIND):
        rnd = ranF(0, 1)
        NumTemp = HalfSear(rnd, 0, NUMIND, TemFitn)
        pop.indv[i] = pop.indv[NumTemp]

'''选择算子'''
def select():
    calUp()
    calSub()

# 基因交叉
# random.randint(a, b) 函数可以生成一个在区间 [a, b] 内的随机整数
# range(i, LENIND) 会生成一个范围对象
def swithstr(x1,x2):
    w1 = random.uniform(0.6, 0.8)
    w2 = 1 - w1
    for i in range(x_dim):
        temp = w1*x1[i]+w2*x2[i]
        x2[i] = w1 * x2[i] + w2 * x1[i]
        x1[i] = temp
        #范围检查
        x1[i] = check_border(x1[i])
        x2[i] = check_border(x2[i])
    return x1,x2

'''三点交叉'''
def swithstr2(x1,x2,x3):
    for i in range(x_dim):
        mean = (x1[i]+x2[i]+x3[i])/3
        x1[i] = (1 + 0.01)*(x1[i]-mean)
        x2[i] = (1 + 0.01) * (x2[i] - mean)
        x3[i] = (1 + 0.01) * (x3[i] - mean)
        #范围检查
        x1[i] = check_border(x1[i])
        x2[i] = check_border(x2[i])
        x3[i] = check_border(x3[i])
    return x1,x2


#近亲回避
'''交叉算子'''
def crossover():
    for i in range(NUMIND // 2):
        if ranF(0, 1) > crossPro:
            pass
        else:
            j = k = 0
            while j == k:  # 直到jk不相等跳出循环
                j = int(ranF(0, NUMIND))
                ranF(0, 1)
                k = int(ranF(0, NUMIND))
            pop.indv[j].x,pop.indv[k].x = swithstr(pop.indv[j].x, pop.indv[k].x)


#两点变异
def changeM(x):
    # 将传入的二进制串 ind 中某个位置上的 0 和 1 进行互换
    r1 = random.uniform(-0.2, 0.2)  # 扰动强度
    r2 = random.uniform(-0.2, 0.2)  # 扰动强度
    for i in range(x_dim):
        x[i] = check_border(x[i]+r1+r2)
    return x

'''变异算子'''
def mutation():
    for i in range(NUMIND):
        if ranF(0, 1) > mutaPro:
            continue
        else:
            pop.indv[i].x = changeM(pop.indv[i].x)


def max():
    max_fitness = -float("inf")
    max_index = 0
    for i in range(NUMIND):
        if pop.indv[i].fitness > max_fitness:
            max_fitness = pop.indv[i].fitness
            max_index = i
    pop.bestInd = copy.deepcopy(pop.indv[max_index])

def main(FUN1,low1,up1,x_dim1):
    global FUN
    FUN = FUN1
    global up
    up = up1
    global low
    low = low1
    global x_dim
    x_dim = x_dim1
    random.seed(int(time.time()) % 9)
    initPop()
    file_path = ''
    if FUN == 1:
        file_path = "fun1_decimal.txt"
    if FUN == 2:
        file_path = "fun2_decimal.txt"
    with open(file_path, "w") as fp:
        # 3.85027
        while (FUN == 1 and pop.bestInd.fitness < 36300 and pop.generan < 10000) or (FUN == 2 and pop.bestInd.fitness <= 3.85027 and pop.generan < 10000) :
            calFit()
            max()
            print(f"The generation is {pop.generan}, the best fitness is {pop.bestInd.fitness}")
            fp.write(f"Best Individual: {pop.bestInd.x}, {pop.bestInd.fitness}\n")
            select()
            crossover()
            if pop.generan == 100:
                mutaPro = 0.3
                print("The mutation probability is set to 0.3")
            mutation()
            pop.indv[0] = pop.bestInd
            pop.generan += 1
    print("finished\n\n")

if __name__ == "__main__":
    # main(1,-10,10,2)
    #为什么一起会导致下面fun2的直接finished
    main(2,-1,2,1)



